package server

import (
	"fmt"
	"log"
	. "npxy-go/pkg/proxy"
	. "npxy-go/pkg/proxy/message"
	"npxy-go/pkg/proxy/server/rest"
	"npxy-go/pkg/tcp"
)

type bridgeHandler struct {
	tcp.BaseHandler
	bridgeSes          *tcp.Session
	portalSessions     *SessionManager
	portalServers      *portalServerManager
	disconnectCallback func()
	connectCallback    func()
}

func (b *bridgeHandler) Get() *rest.BridgeInfo {
	servers := b.portalServers.servers
	binds := make([]rest.BridgeBinding, 0, len(servers))
	for key := range b.portalServers.servers {
		binds = append(binds, rest.BridgeBinding{
			LocalPort: key.localPort,
			ProxyPort: key.proxyPort,
		})
	}
	return &rest.BridgeInfo{
		Id:       b.bridgeSes.String(),
		Bindings: binds,
	}
}

func (b *bridgeHandler) Create(meta *rest.BridgeBinding) error {
	b.ListenOnPort(meta.LocalPort, meta.ProxyPort)
	return nil
}

func (b *bridgeHandler) Delete(addr string) error {
	panic("implement me")
}

func (b *bridgeHandler) OnMessage(_ *tcp.SessionContext, buf *tcp.Buffer) {
	switch m := Decode(buf).(type) {
	case *DisconnectionMessage:
		b.portalSessions.ReleaseById(m.Id)
	case *ForwardMessage:
		b.portalSessions.Send(m.Id, buf)
	case *ConfigMessage:
		b.listenOnPort(buf, m.LocalPort, m.PortalPort)
	}
}

func (b *bridgeHandler) OnConnected(ctx *tcp.SessionContext) {
	b.bridgeSes = ctx.Session
	b.portalSessions = NewSessionManager()
	b.portalServers = newPortalServerManager()
	if b.connectCallback != nil {
		b.connectCallback()
	}

	log.Println("Bridge connected", ctx.String())
}

func (b *bridgeHandler) OnDisconnected(ctx *tcp.SessionContext) {
	b.portalSessions.ClearAll()
	b.portalSessions = nil
	b.portalServers.closeAll()
	b.portalServers = nil
	if b.disconnectCallback != nil {
		b.disconnectCallback()
	}

	log.Println("Bridge disconnected", ctx.String())
}

func (b *bridgeHandler) ListenOnPort(localPort, proxyPort uint16) {
	buf := tcp.NewBuffer()
	b.listenOnPort(buf, localPort, proxyPort)
}

func (b *bridgeHandler) listenOnPort(buf *tcp.Buffer, localPort, proxyPort uint16) {
	srv := b.newPortalSrv(b.bridgeSes, localPort)
	listenAndServeAsync(srv, proxyPort)
	b.portalServers.add(serverKey{localPort: localPort, proxyPort: proxyPort}, srv)
	//reply
	m := ConfigMessage{
		State:      1,
		LocalPort:  localPort,
		PortalPort: proxyPort,
	}
	m.Encode(buf)
	b.bridgeSes.Send(buf)
}

func (b *bridgeHandler) newPortalSrv(bridgeSes *tcp.Session, localPort uint16) *tcp.Server {
	srv := tcp.NewServer(func(pipe tcp.Pipeline) {
		pipe.AddHandlerChain(&portalHandler{
			localPort:      localPort,
			portalSessions: b.portalSessions,
			bridgeSession:  bridgeSes,
		})
	})
	srv.ReadBufferSize = tcp.DefaultReadBufferSize
	srv.ReadHeadSize = TotalHeadSize
	return srv
}

func listenAndServeAsync(srv *tcp.Server, port uint16) {
	go func() {
		if err := srv.ListenAndServe(fmt.Sprintf(":%d", port)); err != nil {
			log.Println(err)
		}
	}()
}
